package com.hisense.ovcloud.warehouse.service;

import cn.hutool.crypto.digest.DigestUtil;
import com.hisense.ovcloud.commons.tools.AES256CbcUtil;
import com.hisense.ovcloud.commons.tools.AESEcbEncryptUtil;
import com.hisense.ovcloud.warehouse.Constants;
import com.hisense.ovcloud.warehouse.config.PemProperties;
import com.hisense.ovcloud.warehouse.config.ReadShardingTablesConfig;
import com.hisense.ovcloud.warehouse.config.WorkPoolProperties;
import com.hisense.ovcloud.warehouse.dto.common.CertDto;
import com.hisense.ovcloud.warehouse.dto.req.FetchLatestCertInnerReq;
import com.hisense.ovcloud.warehouse.dto.req.FetchLatestCertReq;
import com.hisense.ovcloud.warehouse.dto.resp.FetchLatestCertInnerResp;
import com.hisense.ovcloud.warehouse.dto.resp.FetchLatestCertResp;
import com.hisense.ovcloud.warehouse.tool.FileTool;
import io.smallrye.mutiny.tuples.Tuple2;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;

@Service
@Slf4j
public class PemService implements InitializingBean {

    @Value("${pem.enableUpdateFeatureCodes:}")
    private String enableUpdateFeatureCodes;

    @Value("${pem.latestCertVersion:1}")
    private int latestCertVersion;

    private final DevicesService devicesService;

    private static Scheduler sqlWriterPool;

    private final WorkPoolProperties workPoolProperties;

    private final PemProperties pemProperties;
    public static CopyOnWriteArrayList<String> pemFilesContents = new CopyOnWriteArrayList<>();

    public PemService(PemProperties pemProperties, DevicesService devicesService, WorkPoolProperties workPoolProperties) {
        this.pemProperties = pemProperties;
        this.devicesService = devicesService;
        this.workPoolProperties = workPoolProperties;
    }

    public boolean versionIsNotUpToDate(int version) {
        return version < latestCertVersion;
    }

    public boolean versionIsUpToDate(int version) {
        return version == latestCertVersion;
    }

    @Override
    public void afterPropertiesSet() {
        sqlWriterPool = Schedulers.newBoundedElastic(workPoolProperties.getSqlWriterPoolCap(), workPoolProperties.getSqlWriterPoolQueuelength(), "sql-writer-pool");
        pemProperties.getFiles().forEach(path -> {
            try {
                pemFilesContents.add(FileTool.readSingleFilesToString(path));
            } catch (IOException e) {
                log.error("read pem file error: {}", e.getMessage(), e);
            }
        });
    }

    private Mono<Tuple2<Boolean, String>> checkPemReq(FetchLatestCertReq r) {
        return devicesService.getPublicKeyInner(Mono.just(r.getWifiId()))
                .zipWhen(pbk -> {
                            boolean flag = Optional.ofNullable(r.getSign()).orElse("")
                                    .equals(DigestUtil.sha256Hex(pbk + r.getWifiId() + r.getNonce()));
                            if (!flag) {
                                log.info("wifiId: {} , sign in req: {}, sign calculated by warehouse: {}", r.getWifiId(), r.getSign(), DigestUtil.sha256Hex(pbk + r.getWifiId() + r.getNonce()));
                            }
                            return Mono.just(Tuple2.of(flag, pbk));
                        },
                        (x, y) -> y);
    }


    public Mono<FetchLatestCertResp> fetchLatestCert(Mono<FetchLatestCertReq> req) {
        return req.flatMap(r -> checkPemReq(r)
                .zipWhen(tup -> {
                    if (!tup.getItem1()) {
                        return Mono.just(FetchLatestCertResp.builder().code(Constants.RESULT_FAILED).pems(new ArrayList<>()).build());
                    } else {
                        return Mono.just(FetchLatestCertResp.builder().code(Constants.RESULT_SUCCESS)
                                .version(latestCertVersion)
                                .pems(
                                        pemFilesContents.stream().map(f -> {
                                            String encryptPem = r.isEnableCBC() ? AES256CbcUtil.encrypt256CBCWithoutIv(tup.getItem2(), f.getBytes(StandardCharsets.UTF_8)) :
                                                    AESEcbEncryptUtil.aesEcbEncryptToBase64(tup.getItem2(), f.getBytes(StandardCharsets.UTF_8));
                                            return CertDto.builder()
                                                    .encryptPem(encryptPem)
                                                    .sign(DigestUtil.sha256Hex(encryptPem))
                                                    .build();
                                        }).collect(Collectors.toList())
                                )
                                .build()
                        );
                    }
                }, (x, y) -> y));
    }

    private boolean checkFeatureCode(String wifiId) {
        if (StringUtils.isEmpty(enableUpdateFeatureCodes)) {
            return false;
        } else if (enableUpdateFeatureCodes.strip().equals("*")) {
            return true;
        } else {
            return Arrays.stream(enableUpdateFeatureCodes.split(",")).anyMatch(wifiId::startsWith);
        }
    }

    public Mono<FetchLatestCertInnerResp> fetchLatestCertInner(Mono<FetchLatestCertInnerReq> wifiId) {
        return wifiId.flatMap(r -> {
                    if (checkFeatureCode(r.getWifiId())) {
                        log.info("wifiId: {} , version: {}, need update", r.getWifiId(), r.getVersion());
                        return devicesService.fetchDeviceInfoCache(r.getWifiId())
                                .zipWhen(deviceInfoInCache -> {
                                    if (StringUtils.isEmpty(deviceInfoInCache.getPlainPubKey())) {
                                        return Mono.just(FetchLatestCertInnerResp.builder().code(Constants.RESULT_FAILED).build());
                                    } else {
                                        if (r.getVersion() > 0 && r.getVersion() <= latestCertVersion && r.getVersion() > deviceInfoInCache.getPemVersion()) {
                                            log.info("wifiId: {} , version: {}, update pem_version", r.getWifiId(), r.getVersion());
                                            Mono.zip(
                                                            Mono.just(devicesService.updatePemVersion(ReadShardingTablesConfig.domainsList.getFirst(), r.getWifiId(), r.getVersion())),
                                                            Mono.just(devicesService.updatePemVersionInCache(r.getWifiId(), r.getVersion()))
                                                    )
                                                    .subscribeOn(sqlWriterPool).subscribe();
                                        }
                                        if (r.getVersion() < latestCertVersion) {
                                            return Mono.just(
                                                    FetchLatestCertInnerResp.builder()
                                                            .needUpdate(true)
                                                            .version(latestCertVersion)
                                                            .build()
                                            );
                                        }else{
                                            return Mono.just(FetchLatestCertInnerResp.builder().build());
                                        }
                                    }
                                }, (x, y) -> y);
                    }

                    return Mono.just(FetchLatestCertInnerResp.builder().build());
                }
        );
    }
}
